#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
#include <functional>

using namespace std;
using TensorType = vector<float>;

class Variable{
public:
    Variable(const shared_ptr<TensorType>& data) {
        this->data = data;
    }

    shared_ptr<TensorType> data;
};

class Function {
public:
    Variable operator()(const Variable& input) {
        auto x = input.data;
        auto y = this->forward(x);

        return Variable(y);
    }
protected:
    virtual shared_ptr<TensorType> forward(const shared_ptr<TensorType>& in) = 0;

};

class Square : public Function {
protected:
    shared_ptr<TensorType> forward(const shared_ptr<TensorType>& in) override {
        auto out = make_shared<TensorType>();
        for (auto e: *in) {
            out->push_back(e * e);
        }

        return out;
    }
};

class Exp : public Function {
protected:
    shared_ptr<TensorType> forward(const shared_ptr<TensorType>& in) override {
        auto out = make_shared<TensorType>();
        for (auto e: *in) {
            out->push_back(exp(e));
        }

        return out;
    }
};

Variable f(const Variable& x) {
    Square A;
    Exp B;
    Square C;

    return C(B(A(x)));
}

Variable numeric_diff(function<Variable(const Variable&)> f, const Variable& x, float eps = 1e-4) {
    auto t0 = make_shared<TensorType>();
    auto t1 = make_shared<TensorType>();

    for (auto e: *(x.data)) {
        t0->push_back(e - eps);
        t1->push_back(e + eps);
    }

    Variable x0(t0);
    Variable x1(t1);
    auto y0 = f(x0);
    auto y1 = f(x1);

    auto diff = make_shared<TensorType>();
    for (size_t i = 0; i < y0.data->size(); ++i) {
        diff->push_back(((*y1.data)[i] - (*y0.data)[i]) / (2 * eps) );
    }

    return Variable(diff);
}


int main() {
    {
        auto data = make_shared<TensorType>(TensorType{2});
        Variable x(data);

        auto f = [](const Variable& x) -> Variable {
            Square A;
            return A(x);
        };

        auto dy = numeric_diff(f, x);
        cout << (*dy.data)[0] << endl;
    }

    {
        auto data = make_shared<TensorType>(TensorType{0.5});
        Variable x(data);

        auto dy = numeric_diff(f, x);
        cout << (*dy.data)[0] << endl;
    }

    return 0;
}